#include "hash.h"



Hash::Hash(std::string& nombre, unsigned int tam_bloque, unsigned int tam_bloque_indice):ArchivoDatos(nombre, tam_bloque, tam_bloque_indice){
	std::string aux = nombre + EXT_EELL;
	buckets_libres = new ArchEspaciosLibres(aux,TAM_EELL_BUCKETS);
	aux = nombre + EXT_TABLA_DISP;
	tabla = new ArchTablaDispersion(aux,tam_bloque_indice/*TAM_BLOQUE_TALBA_DISP*/);
	indice_secundario = NULL;
}

Hash::~Hash(){
	//Si el archivo esta abierto, lo cerramos
	this->cerrar();
	if(indice_secundario != NULL )
		delete indice_secundario;
	delete(buckets_libres);	
	delete (tabla);
}

void Hash::map_hash(){
	/*std::cout << "<  TABLA HASH  >Tamanio Tabla: ";
    std::cout << this->tabla->get_size();
   	std::cout << "-- Contenido: ";
    for (unsigned int i=0;i<this->tabla->get_size();i++){
    	std::cout << this->tabla->get_valor(i) << "-";
		std::cout << std::flush;
    }
    std::cout <<"\n";*/
    
	
	if( Debug::get_instance()->esta_activado() ){
	/*	std::string msj = "<  TABLA HASH  > Tamanio Tabla: ";
		std::stringstream ss;
		ss<<this->tabla->get_size();
		msj+= ss.str();
		ss.str("");
		msj+= "--Contenido: ";
		for (unsigned int i=0;i<this->tabla->get_size();i++){
			ss << this->tabla->get_valor(i);
			msj+=ss.str();
			ss.str("");
			if (i < (this->tabla->get_size() - 1)) msj+=",";
		}
		(Debug::get_instance())->mostrar_mensaje(msj);
		*/
	}
}

void Hash::abrir() throw(ExcepAbriendoArch){
	//Si el archivo de datos esta abierto, arrojo excep
	std::string msj = "< ARCHIVO HASH > Se intenta abrir el archivo ";
	msj.append(this->get_nombre());
	(Debug::get_instance() )->mostrar_mensaje(msj);
	if (this->fd != -1)
		throw ExcepAbriendoArch("Error - El archivo: " + this->get_nombre() + " ya estaba abierto.");
	else {
		std::string dest = "./DATA/" + this->get_nombre();
		this->fd = ManejadorArchivos::get_instance()->abrir_archivo(dest);
		if ( this->fd == -1)
			throw ExcepAbriendoArch (ManejadorErrores::get_msg_error(errno) + " Archivo: " + this->get_nombre());
		buckets_libres->abrir(); //arroja una excepcion en caso de no poder abrirlo
		buckets_libres->agregar_observador(Buffer::get_instance());
		this-> cant_bloques = 0; //get_cant_bloques();
		tabla->abrir(); //arroja una excepcion en caso de no poder abrirlo
		tabla->agregar_observador(Buffer::get_instance());	
		if(this->indice_secundario != NULL)
			indice_secundario->abrir();
		this-> cant_bloques = get_cant_bloques_header();
	}	
}
				
unsigned int Hash::get_profundidad(Bloque* bloque){
	unsigned int result = 0;
	//Leo desde el bloque en la pos 0 la cant de registros
	bloque->leer((char*)&result, 0, sizeof(unsigned int));
	return (result);
}

void Hash::set_profundidad(Bloque * bloque, unsigned int prof){
	bloque->escribir((char*)&prof, 0, sizeof(unsigned int));
}

void Hash::duplicar_profundidad(Bloque* bloque){
	unsigned int prof = get_profundidad(bloque);
	prof *= 2;
	bloque->escribir((char*)&prof, 0, sizeof(unsigned int));	
}
void Hash::dividir_profundidad(Bloque* bloque){
	unsigned int prof = get_profundidad(bloque);
	if( prof != 1)
		prof /= 2;
	bloque->escribir((char*)&prof, 0, sizeof(unsigned int));	
}
				
void Hash::crear(std::string formato) throw(ExcepCreandoArch, ExcepSintaxisParser, ExcepBloqueTam){
	std::string msj = "< ARCHIVO HASH > Se intenta crear el archivo ";
	msj.append("./DATA/" + this->get_nombre());
	(Debug::get_instance() )->mostrar_mensaje(msj);
	if(this->get_tam_bloque() < TAM_OCUPADO_CABECERA)
		throw ExcepBloqueTam("Error - No se pudo crear el archivo. El tamaño del bloque es muy pequeno para la cabecera");
	else{
		try{
			Archivo::cargar_registro_base(formato);
			TipoStruct * reg = get_registro_base();
			if(reg->get_tamanio() == 0){
				throw ExcepSintaxisParser("Error - No se ingreso formato para el archivo");
			}
			std::string dest = "./DATA/" + this->get_nombre();
			this->fd = ManejadorArchivos::get_instance()->crear_archivo(dest);
			if ( this->fd == -1 )
				throw ExcepCreandoArch (ManejadorErrores::get_msg_error(errno) + " Archivo: " + this->get_nombre());
			buckets_libres->crear(); //arroja una excepcion en caso de no poder crearlo.
			buckets_libres->agregar_observador(Buffer::get_instance());
			tabla->crear(); //arroja una excepcion en caso de no poder crearlo.
			tabla->agregar_observador(Buffer::get_instance());
	
			//Crea el header, y le guarda el formato
			dest = this->get_nombre() + ".head";
			Header head(dest);
			head.crear();
			head.guardar(formato, this);
			head.cerrar();
			
			
			//creo el bloque 0 para guardar la cantidad de bloques existentes en el archivo
			Bloque* bloque_header = new Bloque(0, this->get_tam_bloque());
			bloque_header->escribir((char*)&this->cant_bloques, 0, sizeof(unsigned int));
			Buffer::get_instance()->guardar_bloque(*this, bloque_header);
			buckets_libres->append(0);
			delete bloque_header;
			//Creo el bloque 1 y lo guardo, lo vamos a usar para primera entrada de la tabla
			Bloque* bloque = new Bloque(1, this->get_tam_bloque());
			set_profundidad(bloque,1);
			tabla->append(1);
			buckets_libres->append( 0);
			Buffer::get_instance()->guardar_bloque(*this, bloque);
			delete (bloque);
			this->cant_bloques = 2;	
		}
		catch(ExcepSintaxisParser &e){
			throw e; //para hacerlo explicito
		}
	}
	map_hash();	
}
		
void Hash::cerrar() {
	if(fd != -1){
		(Debug::get_instance() )->mostrar_mensaje("< ARCHIVO HASH > Se cierra el archivo" + this->get_nombre());
		set_cant_bloques_header(this->cant_bloques);
		this->set_cambio();
		this->avisar_observadores(NULL);
		ManejadorArchivos::get_instance()->cerrar_archivo(this->fd);
		buckets_libres->cerrar();
		tabla->cerrar();
		this->fd = -1;
		
	}
}
		
void Hash::ingresar(TipoStruct * reg) throw(ExcepArchCerrado,ExcepRegExistente, ExcepBloqueTam){
	if( fd != -1 )
		ingresar_interno(reg,false);
	else
		throw ExcepArchCerrado("Error - El archivo : "+ this-> get_nombre() +" esta cerrado. ");
}

void Hash::ingresar_interno(TipoStruct * reg, bool reinserto) throw(ExcepArchCerrado,ExcepRegExistente, ExcepBloqueTam){
	(Debug::get_instance() )->mostrar_mensaje("< HASH > Se ingresa un reg ");
	Bloque* bloque_actual = NULL;
	bloque_actual = get_bloque(reg);
	unsigned int pos,size;
	TipoStruct* reg_base = (TipoStruct*)this->get_registro_base()->clonar();
	bool encontro = bloque_actual->buscar_por_clave_desde(TAM_TAG_PROF, &((*reg)[0]),reg_base, pos, size);
	delete reg_base;
	if (!encontro){
		unsigned int tam = reg->get_tamanio_serializado();
		//Verifico que el registro tenga menor tamanio que el bloque
		if( tam <= this->get_tam_bloque() - 8){ 
			//Pongo el registro en el bucket
			poner_reg_en_bloque(bloque_actual, reg);
			if( (indice_secundario != NULL) && (!reinserto) )
				this->indice_secundario->ingresar(reg);
			delete (bloque_actual);
		}
		else{
			delete bloque_actual;
			throw ExcepBloqueTam("Error - El dato a ingresar es mas grande que el tamano del bloque de " + this->get_nombre() + ". ");
		}
	}
	else{
		delete bloque_actual;
		throw ExcepRegExistente("Error - El registro que intenta ingresar ya existe en el archivo. ");
	}
	map_hash();
}

		
void Hash::quitar(std::vector<ParesDatos*> &elementos, std::vector<TipoStruct*> &resultado) throw(ExcepArchCerrado){
	(Debug::get_instance() )->mostrar_mensaje("< HASH > Se quitan registros ");
	if (this-> fd != -1){
		if( (elementos.size() != 1) || (elementos[0]->get_numero_campo() != 0) ){
			if(!quitar_indice_secundario(elementos,resultado))
				quitar_secuencial(elementos,resultado);
		}			
		else{ //quito por campo 0, hash comun
			Bloque* bloque_actual = NULL;
			FuncionHash h;
			//hash asegura que el bloque existe
			int num_bloque = tabla->get_valor( h(*elementos[0]->get_valor()) % tabla->get_size() ); 
			//Obtengo el bucket desde el buffer
			bloque_actual = Buffer::get_instance()->get_bloque(*this, num_bloque);
			unsigned int pos,size;
			TipoStruct* reg_base = (TipoStruct*)this->get_registro_base()->clonar();
			bool encontro = bloque_actual->buscar_por_clave_desde(TAM_TAG_PROF, elementos[0]->get_valor(),reg_base, pos, size);
			if (encontro){
				sacar_reg_bloque(bloque_actual, reg_base, pos, size); 
				resultado.push_back(reg_base);
				if(indice_secundario != NULL){
					indice_secundario->quitar(reg_base);	
				}
				
				
			}
			else
				delete reg_base;
			
			delete bloque_actual;
			}
		}
	map_hash();
}

void Hash::consultar(std::vector<ParesDatos*> &consultas, std::vector<TipoStruct*> &resultado)
												 throw(ExcepArchCerrado,ExcepLeerBloque){
	if (this-> fd != -1){
		if( (consultas.size() != 1) || (consultas[0]->get_numero_campo() != 0) ){
			if(!consultar_indice_secundario(consultas,resultado))
				consultar_secuencial(consultas,resultado);
		}
		else{
			Bloque* bloque_actual = NULL;
			FuncionHash h;
			//hash asegura que el bloque existe
			int num_bloque = tabla->get_valor( h(*consultas[0]->get_valor()) % tabla->get_size()) ; 
			//Obtengo el bucket desde el buffer
			bloque_actual = Buffer::get_instance()->get_bloque(*this, num_bloque);
			unsigned int pos,size;
			TipoStruct* reg_base = (TipoStruct*)this->get_registro_base()->clonar();
			bool encontro = bloque_actual->buscar_por_clave_desde(TAM_TAG_PROF, consultas[0]->get_valor(),reg_base, pos, size);
			if (encontro)
				resultado.push_back(reg_base);
			else
				delete reg_base;
			delete bloque_actual;
		}
	}
	else
		throw ExcepArchCerrado("Error - El archivo : "+ this-> get_nombre() +" esta cerrado. ");					 	
	map_hash();
}

void Hash::actualizar(TipoStruct * reg) throw(ExcepArchCerrado,ExcepRegInexistente,ExcepBloqueTam){
	if (this-> fd != -1){
		Bloque* bloque_actual = NULL;
		bloque_actual = get_bloque(reg);
		unsigned int pos,size;
		TipoStruct* reg_base = (TipoStruct*)this->get_registro_base()->clonar();
		//busco el registro
		bool encontro = bloque_actual->buscar_por_clave_desde(TAM_TAG_PROF, &((*reg)[0]),reg_base, pos, size);
		if (encontro){
			unsigned int tam = reg->get_tamanio_serializado();
			//Verifico que el registro tenga menor tamanio que el bloque
			if( tam <= this->get_tam_bloque() - 8){ 
				//Saco el reg del bloque
				bloque_actual->comprimir(pos, size); 
				Buffer::get_instance()->guardar_bloque(*this, bloque_actual);
				//Pongo el registro en el bucket
				poner_reg_en_bloque(bloque_actual, reg);
				//actualizo el indice secundario
				if(indice_secundario != NULL){
					indice_secundario->quitar(reg_base);	
					indice_secundario->ingresar(reg);	
				}
				(Debug::get_instance() )->mostrar_mensaje("< SE GUARDA BLOQUE EN DISCO > ");
				delete (bloque_actual);
			}
			else{
				delete reg_base;
				delete bloque_actual;
				throw ExcepBloqueTam("Error - El dato a ingresar es mas grande que el tamano del bloque de " + this->get_nombre() + ". ");
			}
		}
		else{
			delete reg_base;
			delete bloque_actual;
			throw ExcepRegInexistente("Error - El registro que intenta ingresar no existe en el archivo. ");
		}
		delete reg_base;
		
	}else
		throw ExcepArchCerrado("Error - El archivo : "+ this-> get_nombre() +" esta cerrado. ");
}
 													
TiposArchivo Hash::get_tipo_archivo(){
	return HASH;
}

void Hash::eliminar() {
	//Elimino el archivo de espacios libres
	buckets_libres->eliminar();
	//Informo en el debug
	(Debug::get_instance() )->mostrar_mensaje("< ELIMINACION ARCHIVO HASH >");
	//Elimino el archivo de header
	std::string header = "./DATA/"+get_nombre()+".head";
	remove(header.c_str());
	tabla->eliminar();
	this->cerrar();
	//Elimino el indice secundario si lo tiene
	if (this->indice_secundario != NULL)
		this->indice_secundario->eliminar();
	//Lo Elimino
	std::string mi_ruta = "./DATA/"+get_nombre();
	remove(mi_ruta.c_str()); 
}

unsigned int Hash::get_espacio_libre() throw(ExcepArchCerrado, ExcepLeerBloque){
	unsigned int tam_libre = 0;
	unsigned int cont = 0;
	Bloque* bloque_actual = NULL;
	
	while (cont < cant_bloques){ //recorro todos los bloques
		try{
			bloque_actual = Buffer::get_instance()->get_bloque(*this,cont);
			tam_libre += bloque_actual->get_espacio_libre();
		}catch (ExcepLeerBloque &e){
			if (bloque_actual != NULL) { 
				delete (bloque_actual); 
				bloque_actual = NULL;
			}
			throw e;	//la sigo propagando por ahora
		}
		if (bloque_actual != NULL) { 
			delete (bloque_actual); 
			bloque_actual = NULL;
		}
		cont++;
	}
	
	//tam_libre =  this->buckets_libres->get_espacio_libre_datos();
	
	//tam_libre += (this->get_tam_bloque() - 12);//le saco lo que ocupa la cantidad de bloques almacenada
		
	return tam_libre ;
}

unsigned int Hash::get_esp_libre_indice()  throw(ExcepArchCerrado, ExcepLeerBloque){
	return this->tabla->get_espacio_libre();
}
 		 									
unsigned int Hash::get_cant_regs_indice() throw(ExcepArchCerrado){
	return this->tabla->get_cant_regs();
}

long Hash::get_tam_arch_indice() throw(ExcepArchCerrado) {
	return this->tabla->get_tam();
}
 		
Bloque* Hash::get_bloque(TipoStruct * reg){
	FuncionHash h;
	//hash asegura que el bloque existe
	int num_bloque = tabla->get_valor( h((*reg)[0]) % (tabla->get_size())); 
	//Obtengo el bucket desde el buffer
	return Buffer::get_instance()->get_bloque(*this, num_bloque);
}

void Hash::manejar_underflow(Bloque * bloque, unsigned int e_tabla_viejo){
	
	(Debug::get_instance() )->mostrar_mensaje("<HASH> Se produjo un underflow "+ this->get_nombre());
	unsigned int prof_borrado = get_profundidad(bloque);
	//obtengo la posicion que se encuentra a una distancia profundidad div 2
	//del bloque que se quedo vacio. Como puedo estar cerca del final de la tabla
	//opero con el mod para volver al principio.
	unsigned int e_tabla_nuevo = ( (prof_borrado/2) + e_tabla_viejo ) % tabla->get_size() ;
	//obtengo el registro a distancia prof div 2
	Bloque * bloque_nuevo = Buffer::get_instance()->get_bloque(*this, tabla->get_valor(e_tabla_nuevo));
	unsigned int prof_nuevo = get_profundidad(bloque_nuevo);
	
	//si tienen la misma profundidad quiere decir que se puede borrar,
	//si no, no se puede quitar el bloque
	if( prof_nuevo == prof_borrado){
		(Debug::get_instance() )->mostrar_mensaje("<HASH> Se puede liberar el bloque "+ this->get_nombre());
		//la entrada de la tabla que fue borrado el bloque apunta a el 
		//bloque nuevo asi como todas las que le siguen circularmente con
		//la distancia de profundidad del viejo
		try{
			tabla->actualizar_circular(e_tabla_viejo, prof_borrado, bloque_nuevo->get_num_bloque() );
			//pongo al bloque como libre en el arch de espacios libres
			buckets_libres->set_valor(bloque->get_num_bloque(), bloque->get_size() - TAM_TAG_PROF - 4);
			//guardo el bloque nuevo con la mitad de la profundidad
			dividir_profundidad(bloque_nuevo);
			Buffer::get_instance()->guardar_bloque(*this, bloque_nuevo);
			tabla->dividir();
		}
		catch(ExcepArchCerrado &e1){
			delete bloque_nuevo;
			throw e1;
		}
		catch(ExcepEscribirBloque &e2){
			delete bloque_nuevo;
			throw e2;
		}
		catch(ExcepLeerBloque	 &e3){
			delete bloque_nuevo;
			throw e3;
		}
	}
	delete bloque_nuevo;
		
}
int Hash::sacar_reg_bloque(Bloque* bloque, TipoStruct * reg, unsigned int pos_reg, unsigned int tam_reg) {
	//Saco el reg del bloque
	bloque->comprimir(pos_reg, tam_reg);
	//Si el bloque quedo vacio, manejo el underflow
	if( (bloque->get_espacio_libre() + TAM_TAG_PROF + 4) == bloque->get_size() ){
		//obtengo la entrada de la tabla donde estaba el borrado
		FuncionHash h;
		int entrada_tabla_hash = h((*reg)[0]) % tabla->get_size(); 
		manejar_underflow(bloque, entrada_tabla_hash);
	}
	//lo devuelvo a disco despues de haber sacado el registro
	Buffer::get_instance()->guardar_bloque(*this,bloque);
	
	return 0;
}

Bloque * Hash::obtener_bloque_nuevo(Bloque * bloque){
	
	int num_bloque = buckets_libres->consultar_espacio_libre(bloque->get_size() - TAM_TAG_PROF - 4);
	Bloque * bloque_nuevo = NULL;
		
	if(num_bloque == -1 ){
		//si no existe, creo un nuevo bloque, lo agrego al arch de espacios
		//libres como libre y lo guardo en disco
		num_bloque = this->cant_bloques;
		this->cant_bloques++;
		buckets_libres->append(0);
		bloque_nuevo = new Bloque(num_bloque, bloque->get_size());
	}
	else{
		bloque_nuevo = Buffer::get_instance()->get_bloque(*this,num_bloque);	
		//lo setteo no disponible
		buckets_libres->set_valor(bloque_nuevo->get_num_bloque(), 0);
	}
	//les asigno la misma profundidad
	set_profundidad(bloque_nuevo, get_profundidad(bloque));
	try{
			Buffer::get_instance()->guardar_bloque(*this,bloque_nuevo);	
		}
	catch(ExcepEscribirBloque &e){
		delete bloque_nuevo;
		throw e;
	}
	return bloque_nuevo;
	
}

void Hash::redistribur(Bloque * bloque, TipoStruct *reg){
	
	(Debug::get_instance() )->mostrar_mensaje("<HASH> Redistribuyo overflow "+ this->get_nombre());
	//saco todos los registros del bloque mas el registro nuevo a agregar
	//y vuelvo a ingresar
	std::vector<TipoStruct *> elementos_bucket;
	TipoStruct * reg_aux = (TipoStruct*)this->get_registro_base()->clonar();
	bloque->get_registros_desde (reg_aux, elementos_bucket,TAM_TAG_PROF);
	
	delete reg_aux;
	reg_aux = (TipoStruct*)reg->clonar();
	elementos_bucket.push_back(reg_aux);
	//guardo la profundidad ya que al vaciar se borra
	unsigned int profundidad = get_profundidad(bloque);
	bloque->vaciar();
	set_profundidad(bloque, profundidad);
	try{
		Buffer::get_instance()->guardar_bloque(*this,bloque);
	}
	catch(ExcepEscribirBloque &e ){
		std::vector<TipoStruct *>::iterator it = elementos_bucket.begin();
		while(it != elementos_bucket.end() ){
			delete (*it);
			it++;
		}
		throw e;
	}
	
	
	//los recorro todos insertandolos de a uno
	std::vector<TipoStruct *>::iterator it = elementos_bucket.begin();
	while(it != elementos_bucket.end() ){
		ingresar_interno(*it, true);
		delete (*it);
		it++;
	}

}

void Hash::manejar_overflow(Bloque * bloque, TipoStruct * reg){
	(Debug::get_instance() )->mostrar_mensaje("<HASH> Se produjo un overflow en "+ this->get_nombre());
	//manejo del overflow
	unsigned int prof_bloque = get_profundidad(bloque);
	//como en todos los overflow, duplico la profundidad
	duplicar_profundidad(bloque);
	//obtengo un bloque nuevo, que es donde se va a meter el overflow
	//a partir del que desbordo
	Bloque * bloque_nuevo = obtener_bloque_nuevo(bloque);
	//obtengo el numero de entrada que genero el overflow
	FuncionHash h;
	unsigned int entrada_tabla = h((*reg)[0]) % tabla->get_size();
	//si el bucket desbordado tiene la misma profundidad que la tabla,
	//la duplico
	try{
		if(  prof_bloque == tabla->get_size() ){
			(Debug::get_instance() )->mostrar_mensaje("<HASH> Se duplica la tabla de hash de "+ this->get_nombre());
			tabla->duplicar();
		}
		//actualizo en forma circular moviendome en saltos de la nueva
		//profundidad. Si se duplico la tabla, el recorrido circular va a
		//caer dos veces consecutivas en el mismo lugar entonces se
		//puede usar siempre
		tabla->actualizar_circular(entrada_tabla, get_profundidad(bloque), bloque_nuevo->get_num_bloque() );
		//redistribuyo todos los elementos
		redistribur(bloque, reg);
	}
	catch(ExcepArchCerrado &e1){
		delete bloque_nuevo;
		throw e1;
	}
	catch(ExcepEscribirBloque &e2){
		delete bloque_nuevo;
		throw e2;
	}
	catch(ExcepLeerBloque	 &e3){
		delete bloque_nuevo;
		throw e3;
	}
	delete bloque_nuevo;
}

bool Hash::poner_reg_en_bloque(Bloque* bloque, TipoStruct* reg){
	//Meto el reg en el bloque
	unsigned int tam_reg = bloque->append(reg);
	if(tam_reg == 0)
		//si no entro
		manejar_overflow(bloque, reg);
	else
		//lo devuelvo a disco despues de haber puesto el reg
		Buffer::get_instance()->guardar_bloque(*this,bloque);
	return true;
}

void Hash::eliminar_bloque (Bloque* bloque){
	if (bloque != NULL) {
		//Invalido el bloque en el buffer para q cuente como libre
		Buffer::get_instance()->invalidar_bloque(*this, bloque->get_num_bloque());
		//MODIFICAR TABLA HASH -------------------------------------------------------------------------
	}
}
